/**
*
* Copyright 2003-2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.geronimo.tomcat.realm;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.deploy.LoginConfig;
import org.apache.catalina.deploy.SecurityConstraint;
import org.apache.catalina.realm.JAASRealm;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.geronimo.security.ContextManager;
import org.apache.geronimo.security.jacc.PolicyContextHandlerContainerSubject;
import org.apache.geronimo.security.realm.providers.CertificateChainCallbackHandler;
import org.apache.geronimo.security.realm.providers.PasswordCallbackHandler;
import org.apache.geronimo.tomcat.JAASTomcatPrincipal;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.AccountExpiredException;
import javax.security.auth.login.CredentialExpiredException;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.security.jacc.PolicyContext;
import javax.security.jacc.PolicyContextException;
import javax.security.jacc.WebResourcePermission;
import javax.security.jacc.WebRoleRefPermission;
import javax.security.jacc.WebUserDataPermission;
import javax.servlet.ServletRequest;
import java.io.IOException;
import java.security.AccessControlContext;
import java.security.AccessControlException;
import java.security.Principal;
import java.security.cert.X509Certificate;
public class TomcatGeronimoRealm extends JAASRealm {
private static final Log log = LogFactory.getLog(TomcatGeronimoRealm.class);
private static ThreadLocal currentRequest = new ThreadLocal();
/**
* Descriptive information about this <code>Realm</code> implementation.
*/
protected static final String info = "org.apache.geronimo.tomcat.TomcatGeronimoRealm/1.0";
/**
* Descriptive information about this <code>Realm</code> implementation.
*/
protected static final String name = "TomcatGeronimoRealm";
public TomcatGeronimoRealm() {
}
public static Request getRequest() {
return (Request) currentRequest.get();
}
public static Request setRequest(Request request) {
Request old = (Request) currentRequest.get();
currentRequest.set(request);
return old;
}
/**
* Enforce any user data constraint required by the security constraint
* guarding this request URI. Return <code>true</code> if this constraint
* was not violated and processing should continue, or <code>false</code>
* if we have created a response already.
*
* @param request Request we are processing
* @param response Response we are creating
* @param constraints Security constraint being checked
* @throws IOException if an input/output error occurs
*/
public boolean hasUserDataPermission(Request request,
Response response,
SecurityConstraint[] constraints)
throws IOException {
//Get an authenticated subject, if there is one
Subject subject = null;
try {
//We will use the PolicyContextHandlerContainerSubject.HANDLER_KEY to see if a user
//has authenticated, since a request.getUserPrincipal() will not pick up the user
//unless its using a cached session.
subject = (Subject) PolicyContext.getContext(PolicyContextHandlerContainerSubject.HANDLER_KEY);
} catch (PolicyContextException e) {
log.error(e);
}
//If nothing has authenticated yet, do the normal
if (subject == null)
return super.hasUserDataPermission(request, response, constraints);
ContextManager.setCurrentCaller(subject);
try {
AccessControlContext acc = ContextManager.getCurrentContext();
/**
* JACC v1.0 secion 4.1.1
*/
String transportType;
if (request.isSecure()) {
transportType = "CONFIDENTIAL";
//What about INTEGRAL?? Does Tomcat support it??
} else {
transportType = "NONE";
}
WebUserDataPermission wudp = new WebUserDataPermission(request.getServletPath(), new String[]{request.getMethod()}, transportType);
acc.checkPermission(wudp);
} catch (AccessControlException ace) {
response.sendError(Response.SC_FORBIDDEN);
return false;
}
return true;
}
/**
* Perform access control based on the specified authorization constraint.
* Return <code>true</code> if this constraint is satisfied and processing
* should continue, or <code>false</code> otherwise.
*
* @param request Request we are processing
* @param response Response we are creating
* @param constraints Security constraints we are enforcing
* @param context The Context to which client of this class is attached.
* @throws java.io.IOException if an input/output error occurs
*/
public boolean hasResourcePermission(Request request,
Response response,
SecurityConstraint[] constraints,
Context context)
throws IOException {
// Specifically allow access to the form login and form error pages
// and the "j_security_check" action
LoginConfig config = context.getLoginConfig();
if ((config != null) &&
(org.apache.catalina.realm.Constants.FORM_METHOD.equals(config.getAuthMethod()))) {
String requestURI = request.getDecodedRequestURI();
String loginPage = context.getPath() + config.getLoginPage();
if (loginPage.equals(requestURI)) {
if (log.isDebugEnabled())
log.debug(" Allow access to login page " + loginPage);
return (true);
}
String errorPage = context.getPath() + config.getErrorPage();
if (errorPage.equals(requestURI)) {
if (log.isDebugEnabled())
log.debug(" Allow access to error page " + errorPage);
return (true);
}
if (requestURI.endsWith(org.apache.catalina.realm.Constants.FORM_ACTION)) {
if (log.isDebugEnabled())
log.debug(" Allow access to username/password submission");
return (true);
}
}
currentRequest.set(request);
// Which user principal have we already authenticated?
Principal principal = request.getUserPrincipal();
//If we have no principal, then we should use the default.
if (principal == null) {
return request.isSecure();
} else {
ContextManager.setCurrentCaller(((JAASTomcatPrincipal) principal).getSubject());
}
try {
AccessControlContext acc = ContextManager.getCurrentContext();
/**
* JACC v1.0 section 4.1.2
*/
acc.checkPermission(new WebResourcePermission(request));
} catch (AccessControlException ace) {
response.sendError(Response.SC_FORBIDDEN);
return false;
}
return true;
}
/**
* Return <code>true</code> if the specified Principal has the specified
* security role, within the context of this Realm; otherwise return
* <code>false</code>.
*
* @param principal Principal for whom the role is to be checked
* @param role Security role to be checked
*/
public boolean hasRole(Principal principal, String role) {
if ((principal == null) || (role == null) || !(principal instanceof JAASTomcatPrincipal)) {
return false;
}
Request request = (Request) currentRequest.get();
assert request != null;
Wrapper servletWrapper = request.getWrapper();
String name = servletWrapper.getName();
/**
* JACC v1.0 secion B.19
*/
if (name == null || name.equals("jsp")) {
name = "";
}
//Set the caller
ContextManager.setCurrentCaller(((JAASTomcatPrincipal) principal).getSubject());
AccessControlContext acc = ContextManager.getCurrentContext();
try {
/**
* JACC v1.0 section 4.1.3
*/
acc.checkPermission(new WebRoleRefPermission(name, role));
} catch (AccessControlException e) {
return false;
}
return true;
}
/**
* Return the <code>Principal</code> associated with the specified
* username and credentials, if there is one; otherwise return
* <code>null</code>.
* <p/>
* If there are any errors with the JDBC connection, executing the query or
* anything we return null (don't authenticate). This event is also logged,
* and the connection will be closed so that a subsequent request will
* automatically re-open it.
*
* @param username Username of the <code>Principal</code> to look up
* @param credentials Password or other credentials to use in authenticating this
* username
*/
public Principal authenticate(String username, String credentials) {
char[] cred = credentials == null? null: credentials.toCharArray();
CallbackHandler callbackHandler = new PasswordCallbackHandler(username, cred);
return authenticate(callbackHandler, username);
}
public Principal authenticate(X509Certificate[] certs) {
if (certs == null || certs.length == 0) {
return null;
}
CallbackHandler callbackHandler = new CertificateChainCallbackHandler(certs);
String principalName = certs[0].getSubjectX500Principal().getName();
return authenticate(callbackHandler, principalName);
}
public Principal authenticate(CallbackHandler callbackHandler, String principalName) {
// Establish a LoginContext to use for authentication
try {
if ( (principalName!=null) && (!principalName.equals("")) ) {
LoginContext loginContext = null;
if (appName == null)
appName = "Tomcat";
if (log.isDebugEnabled())
log.debug(sm.getString("jaasRealm.beginLogin", principalName, appName));
// What if the LoginModule is in the container class loader ?
ClassLoader ocl = null;
if (isUseContextClassLoader()) {
ocl = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
}
try {
loginContext = new LoginContext(appName, callbackHandler);
} catch (Throwable e) {
log.error(sm.getString("jaasRealm.unexpectedError"), e);
return (null);
} finally {
if (isUseContextClassLoader()) {
Thread.currentThread().setContextClassLoader(ocl);
}
}
if (log.isDebugEnabled())
log.debug("Login context created " + principalName);
// Negotiate a login via this LoginContext
Subject subject;
try {
loginContext.login();
Subject tempSubject = loginContext.getSubject();
if (tempSubject == null) {
if (log.isDebugEnabled())
log.debug(sm.getString("jaasRealm.failedLogin", principalName));
return (null);
}
subject = ContextManager.getServerSideSubject(tempSubject);
if (subject == null) {
if (log.isDebugEnabled())
log.debug(sm.getString("jaasRealm.failedLogin", principalName));
return (null);
}
ContextManager.setCurrentCaller(subject);
} catch (AccountExpiredException e) {
if (log.isDebugEnabled())
log.debug(sm.getString("jaasRealm.accountExpired", principalName));
return (null);
} catch (CredentialExpiredException e) {
if (log.isDebugEnabled())
log.debug(sm.getString("jaasRealm.credentialExpired", principalName));
return (null);
} catch (FailedLoginException e) {
if (log.isDebugEnabled())
log.debug(sm.getString("jaasRealm.failedLogin", principalName));
return (null);
} catch (LoginException e) {
log.warn(sm.getString("jaasRealm.loginException", principalName), e);
return (null);
} catch (Throwable e) {
log.error(sm.getString("jaasRealm.unexpectedError"), e);
return (null);
}
if (log.isDebugEnabled())
log.debug(sm.getString("jaasRealm.loginContextCreated", principalName));
// Return the appropriate Principal for this authenticated Subject
/* Principal principal = createPrincipal(username, subject);
if (principal == null) {
log.debug(sm.getString("jaasRealm.authenticateFailure", username));
return (null);
}
if (log.isDebugEnabled()) {
log.debug(sm.getString("jaasRealm.authenticateSuccess", username));
}
*/
JAASTomcatPrincipal jaasPrincipal = new JAASTomcatPrincipal(principalName);
jaasPrincipal.setSubject(subject);
return (jaasPrincipal);
}
else {
if (log.isDebugEnabled())
log.debug("Login Failed - null userID");
return null;
}
} catch (Throwable t) {
log.error("error ", t);
return null;
}
}
/**
* Prepare for active use of the public methods of this <code>Component</code>.
*
* @throws org.apache.catalina.LifecycleException
* if this component detects a fatal error
* that prevents it from being started
*/
public void start() throws LifecycleException {
// Perform normal superclass initialization
super.start();
}
/**
* Gracefully shut down active use of the public methods of this <code>Component</code>.
*
* @throws LifecycleException if this component detects a fatal error
* that needs to be reported
*/
public void stop() throws LifecycleException {
// Perform normal superclass finalization
super.stop();
}
}